#include <assert.h>
#include <cmath>
#include <QDebug>
#include <QtGlobal>
#include "gl_widget.h"

GLWidget::GLWidget(QWidget *parent) : QOpenGLWidget(parent) {
    calcEyePosition();
    m_viewMatrix.lookAt(m_eyePosition
            , QVector3D(0, 0, 0)
            , QVector3D(0, 1, 0));
    m_timer = new QTimer();
    connect(m_timer, &QTimer::timeout, this, &GLWidget::animate);
    m_blueMalletPrePosition = m_blueMalletPosition;
}

GLWidget::~GLWidget() {
    makeCurrent();
    delete m_textureProgram;
    delete m_table;
    delete m_tableDrawer;
    delete m_mallet;
    delete m_malletDrawer;
    delete m_simpleProgram;
    delete m_puck;
    delete m_puckDrawer;
    doneCurrent();
    m_timer->stop();
    delete m_timer;
}

void GLWidget::calcEyePosition() {
    m_eyePosition.setX(eyeDistance * sin(m_theda));
    m_eyePosition.setZ(eyeDistance * cos(m_theda));
}

void GLWidget::animate() {
    m_puckPosition += m_puckVector * 5;
    // 冰球碰到墙壁反射
    if (m_puckPosition.x() <= TABLE_LEFT + PUCK_RADIUS) {
        m_puckPosition.setX(TABLE_LEFT + PUCK_RADIUS);
        m_puckVector.setX(-m_puckVector.x());
    }else if (m_puckPosition.x() >= TABLE_RIGHT - PUCK_RADIUS) {
        m_puckPosition.setX(TABLE_RIGHT - PUCK_RADIUS);
        m_puckVector.setX(-m_puckVector.x());
    }
    if (m_puckPosition.z() >= (TABLE_NEAR - PUCK_RADIUS)) {
        m_puckPosition.setZ(TABLE_NEAR - PUCK_RADIUS);
        m_puckVector.setZ(-m_puckVector.z());
    }else if (m_puckPosition.z() <= TABLE_FAR + PUCK_RADIUS) {
        m_puckPosition.setZ(TABLE_FAR + PUCK_RADIUS);
        m_puckVector.setZ(-m_puckVector.z());
    }
    // 冰球速度递减
    m_puckVector *= 0.999f;
    update();
};

// 在resizeGL(),paintGL()之前调用, 仅调用一次。
void GLWidget::initializeGL() {
    initializeOpenGLFunctions();
    // 设置清屏颜色
    glClearColor(0.f, 0.f, 0.f, 1.0f);
    glEnable(GL_PROGRAM_POINT_SIZE);

    m_simpleProgram= new SimpleShaderProgram(":/glsl/simple_vertex_shader.glsl"
            , ":/glsl/simple_fragment_shader.glsl");
    m_mallet = new Mallet(MALLET_RADIUS, MALLET_HEIGHT, NUMBER_POINTS);
    m_malletDrawer = new MalletDrawer(m_simpleProgram, m_mallet);

    m_textureProgram = new TextureShaderProgram(":/glsl/texture_vertex_shader.glsl"
            , ":/glsl/texture_fragment_shader.glsl");
    m_table = new Table();
    m_tableDrawer = new TableDrawer(m_textureProgram, m_table);

    m_puck = new Puck(PUCK_RADIUS, PUCK_HEIGHT, NUMBER_POINTS);
    m_puck->setColor(0.0f, 1.f, 0.f);
    m_puckDrawer = new PuckDrawer(m_simpleProgram, m_puck);
    // 开始定时器
    m_timer->start(10);
}

void GLWidget::resizeGL(int w, int h) {
    m_projMatrix.setToIdentity();
    float aspect = (float)w / h;
    // 竖直视场角度，宽高比，汇聚点到近平面距离，汇聚点到远平面的距离
    m_projMatrix.perspective(45, aspect, 1.f, 10.f);
    m_viewProjectMatrix = m_projMatrix * m_viewMatrix;
    bool invertible = false;
    m_invertedViewProjectMatrix = m_viewProjectMatrix.inverted(&invertible);
    if (!invertible) {
        qFatal("view project matrix is not invertible");
        exit(EXIT_FAILURE);
    }
}

void GLWidget::positionTableInScene() {
    // 转换矩阵
    QMatrix4x4 modelMatrix;
    modelMatrix.setToIdentity();
    // 绕x轴顺时针旋转90度
    modelMatrix.rotate(90, 1, 0, 0);
    m_modelViewProjectMatrix = m_viewProjectMatrix * modelMatrix;
}

void GLWidget::positionObjectInScene(QVector3D &pos) {
   positionObjectInScene(pos.x(), pos.y(), pos.z());
}

void GLWidget::positionObjectInScene(float x, float y, float z) {
    // 转换矩阵
    QMatrix4x4 modelMatrix;
    modelMatrix.setToIdentity();
    // 绕x轴顺时针选择90度
    modelMatrix.translate(x, y, z);
    m_modelViewProjectMatrix = m_viewProjectMatrix * modelMatrix;
}

void GLWidget::paintGL() {
    // 清空颜色缓冲区和深度缓冲区
    glClear(GL_COLOR_BUFFER_BIT);
    // 桌面
    m_textureProgram->bind();
    positionTableInScene();
    m_tableDrawer->draw(m_modelViewProjectMatrix);
    m_textureProgram->release();
    // 远端木槌(red)
    m_mallet->setColor(1.0f, 0.f, 0.f);
    positionObjectInScene(m_redMalletPosition);
    m_malletDrawer->draw(m_modelViewProjectMatrix);
    // 冰球
    positionObjectInScene(m_puckPosition);
    m_puckDrawer->draw(m_modelViewProjectMatrix);
    // 近端木槌(red)
    m_mallet->setColor(0.f, 0.f, 1.f);
    positionObjectInScene(m_blueMalletPosition);
    m_malletDrawer->draw(m_modelViewProjectMatrix);
}

void GLWidget::mousePressEvent(QMouseEvent *event) {
    // 窗口X座标(0, width)转换为GL的X座标(-1,1)
    float normalizedX = (event->localPos().x() - width() / 2.f) * 2.f / width();
    // 窗口Y座标(0, height)转换为GL的Y座标(-1,1), 方向向上
    float normalizedY = -(event->localPos().y() - height() / 2.f) * 2.f / height();
    Ray ray;
    normalized2D2Ray(ray, normalizedX, normalizedY);
    Sphere blueBoundingSphere;
    blueBoundingSphere.m_center.setX(m_blueMalletPosition.x());
    blueBoundingSphere.m_center.setY(m_blueMalletPosition.y());
    blueBoundingSphere.m_center.setZ(m_blueMalletPosition.z());
    blueBoundingSphere.m_radius = m_mallet->getHeight() / 2.f;
    if (intersect(blueBoundingSphere, ray)) {
        m_blueMalletDraging = true;
    }
}

void GLWidget::normalized2D2Ray(Ray &ray, float x, float y) {
   // 将GL的座标(x,y)转化为起点为近平面上的对应点，方向朝远平面上的对应点的射线(Ray)
   // Ndc：normalized near device coordinate
   QVector4D nearPointNdc{x,y,1,1};
   QVector4D farPointNdc{x,y,-1,1};
   // 转化为世界坐标
   QVector4D nearPointWorld = m_invertedViewProjectMatrix * nearPointNdc;
   QVector4D farPointWorld = m_invertedViewProjectMatrix * farPointNdc;
   // 反转透视除法
   QVector3D nearPoint {nearPointWorld.x() / nearPointWorld.w()
                       , nearPointWorld.y() / nearPointWorld.w()
                       , nearPointWorld.z() / nearPointWorld.w()};
   QVector3D farPoint {farPointWorld.x() / farPointWorld.w()
                       , farPointWorld.y() / farPointWorld.w()
                       , farPointWorld.z() / farPointWorld.w()};
   ray.m_point = nearPoint;
   ray.m_vector = farPoint - nearPoint;
}

void GLWidget::mouseMoveEvent(QMouseEvent *event) {
    if (!m_blueMalletDraging)
        return;
    m_blueMalletPrePosition = m_blueMalletPosition;
    // 窗口X座标(0, width)转换为GL的X座标(-1,1)
    float normalizedX = (event->localPos().x() - width() / 2.f) * 2.f / width();
    // 窗口Y座标(0, height)转换为GL的Y座标(-1,1), 方向向上
    float normalizedY = -(event->localPos().y() - height() / 2.f) * 2.f / height();
    Ray ray;
    normalized2D2Ray(ray, normalizedX, normalizedY);
    Plan plan {{0.f,0.f,0.f}, {0.f,1.f,0.f}};
    // 眼睛->点击处射线与桌子平面的交点
    QVector3D p = intersect(plan, ray);
    // 限制木槌移动范围
    float x = qBound(TABLE_LEFT + MALLET_RADIUS, p.x(), TABLE_RIGHT - MALLET_RADIUS);
    float z = qBound(MALLET_RADIUS, p.z(), TABLE_NEAR - MALLET_RADIUS);
    m_blueMalletPosition.setX(x);
    m_blueMalletPosition.setZ(z);

    blueMalletPuckCollideCheck();
    update();
}

void GLWidget::blueMalletPuckCollideCheck() {
    // 木槌->冰球的矢量
    QVector3D malletPuckVector = m_puckPosition - m_blueMalletPrePosition;
    // y方向不应该有速度
    malletPuckVector.setY(0.f);
    // 距离超过:二者半径之和则表示击中
    if (malletPuckVector.length() <= (PUCK_RADIUS + MALLET_RADIUS) && !m_blueMalletCollided) {
        m_blueMalletCollided = true;
        // 木槌的速度矢量
        QVector3D malletSpeed = m_blueMalletPosition - m_blueMalletPrePosition;
        // 木槌的速度矢量在木槌冰球中心连续上的投影速度
        float scale = QVector3D::dotProduct(malletSpeed, malletPuckVector) / malletPuckVector.length();
        m_puckVector = malletPuckVector * scale;
    }
    if (malletPuckVector.length() > (PUCK_RADIUS + MALLET_RADIUS)) {
        m_blueMalletCollided = false;
    }
}

void GLWidget::mouseReleaseEvent(QMouseEvent *event) {
    m_blueMalletDraging = false;
    m_blueMalletPrePosition = m_blueMalletPosition;
}
